﻿
type RECT = { x: number, y: number, width: number, height: number };
type GRIDPOS = { column: number, row: number };
type orientationType = 0 | 1 | 2 | 3;//0未旋转，此后每加一就顺时针旋转90度
class Tetris {
    constructor(p_canvas: HTMLCanvasElement) {
        this.canvas = p_canvas;
        this.ctx = this.canvas.getContext("2d");
        this.canvas.width = 1080;
        this.canvas.height = 1920;
        this.gameArea = { x: 50, y: 50, width: 400, height: 800 };
        this.reset();
        this.init();

        //this.data[1] = 1;
        //this.data[11] = 1;
        //this.data[12] = 1;
        //this.data[13] = 1;

        this.block = new Block("2");
        this.block.orientation = 3;
    }
    private readonly canvas: HTMLCanvasElement;
    private readonly ctx: CanvasRenderingContext2D;
    /**
     * 游戏计时器
     **/
    private timer: number;
    /**
     * 游戏持续时长
     * */
    private elapsed: number = 0;
    private cellWidth: number;//单元格宽度
    private cellHeight: number;//单元格高度
    private init() {
        this.cellWidth = this.gameArea.width / this.columns;
        this.cellHeight = this.gameArea.height / this.rows;

        //以每秒24帧的速度刷新
        this.timer = setInterval(this.tick.bind(this), 1000 / 24);

    }
    /**
     * 处理每一帧
     * */
    private tick() {
        //暂不确定js是否支持更精确的计时，先用每帧的耗时来累加
        this.elapsed += 1000 / 24.0;//计时
        this.measure();
        this.render();
    }
    /**
     * 列数
     * */
    columns: number = 10;
    /**
     * 行数
     */
    rows: number = 20;
    /**
     * 图块的下落速度（该值记录下落一格应该耗费的时长，单位毫秒）
     * */
    blockDropSpeed = 1000;
    /**
     * 游戏区域
     */
    private gameArea: RECT;

    private data: Array<number>;

    private block?: Block;//当前正下落的方块
    //当前方块的坐标（基于网格）
    private blockPos: GRIDPOS = { column: 0, row: 0 };

    private lastDropElapsed = 0;//记录上一次图块下落的时间
    /**
     * 渲染当前帧
     * */
    render(): void {

        //清除游戏画面
        this.ctx.clearRect(this.gameArea.x, this.gameArea.y, this.gameArea.width, this.gameArea.height)
        //渲染网格
        this.renderGrid();

        //渲染游戏数据
        this.ctx.fillStyle = "red";
        //渲染静止的数据
        for (var row = 0; row < this.rows; row++) {
            for (var column = 0; column < this.columns; column++) {
                var i = row * this.columns + column;
                if (this.data[i] > 0) {
                    this.renderCell(column, row);
                }
            }
        }
        //渲染当前块
        this.renderBlock();
    }
    /**
     * 测算游戏当前帧各元素的状态
     * */
    measure(): void {
        if (this.elapsed - this.lastDropElapsed > this.blockDropSpeed) {
            //此时图块应该下落一格

            //碰撞检测，确认图块没有被已有的格子“顶住”

            this.blockPos.row += 1;//不确定js是否可以这样修改object的参数，稍后测试
            this.lastDropElapsed = this.elapsed;//记录此次下落的时间
        }
    }
    /**
     * 图块的“碰撞检测”
     * @param newColumn 新的行
     * @param newRow 新的列
     * 返回是否可以移动到此位置
     */
    private ensureBlock(newColumn: number, newRow: number): boolean {
        var ps = new Array<GRIDPOS>();//当前块移动到新位置后占用的位置
        var blockData = this.block.getData();
        for (var col = 0; col < 4; col++) {
            for (var row = 0; row < 4; row++) {
                var i = col + (row * 4);
                var d = blockData.data[i];
                if (d > 0) {
                    ps.push({ column: newColumn + col, row: newRow + row });
                }
            }
        }
        for (var row = 0; row < this.rows; row++) {
            for (var column = 0; column < this.columns; column++) {
                var i = row * this.columns + column;
                if (this.data[i] > 0) {
                    this.renderCell(column, row);
                }
            }
        }


        return true;
    }

    //渲染下落的方块
    private renderBlock() {
        if (this.block) {
            var blockData = this.block.getData();
            var c = this.blockPos.column;
            var r = this.blockPos.row;
            for (var col = 0; col < 4; col++) {
                for (var row = 0; row < 4; row++) {
                    var i = col + (row * 4);
                    var d = blockData.data[i];
                    if (d > 0) {
                        this.renderCell(c + col, r + row);
                    }
                }
            }
        }
    }
    /**
     * 渲染单元格
     * @param column
     * @param row
     */
    private renderCell(column: number, row: number) {
        var x = this.gameArea.x + column * this.cellWidth;
        var y = this.gameArea.y + row * this.cellHeight;
        this.ctx.fillRect(x, y, this.cellWidth, this.cellHeight);
    }
    /**
     * 渲染网格线（调试用）
     * */
    private renderGrid() {
        var x = this.gameArea.x, y = this.gameArea.y;
        this.ctx.beginPath();
        this.ctx.strokeStyle = "gray";
        for (var c = 0; c <= this.columns; c++) {
            this.ctx.moveTo(x, y);
            this.ctx.lineTo(x, y + this.gameArea.height);
            x += (this.gameArea.width / this.columns);
        }
        x = this.gameArea.x;
        y = this.gameArea.y;
        for (var r = 0; r <= this.rows; r++) {
            this.ctx.moveTo(x, y);
            this.ctx.lineTo(x + this.gameArea.width, y);
            y += (this.gameArea.height / this.rows);
        }
        this.ctx.closePath();
        this.ctx.stroke();
    }
    /**
     * 重置游戏
     * */
    public reset(): void {
        this.elapsed = 0;
        this.data = new Array<number>(this.columns * this.rows);
    }

    /**
     * 旋转当前块
     * */
    public rotateBlock() {
        if (this.block) {
            var r = <number>this.block.orientation;
            r++;
            this.block.orientation = <orientationType>(r % 4);
        }
    }
}

//#region   图块

type blocksDataType = { "1": Array<number>, "2": Array<number>, "3": Array<number>, "4": Array<number>, "5": Array<number>, "6": Array<number> };


var blocksData: blocksDataType = {
    /*
     OOOO
     OXXO
     OXOO
     OXOO 
     */
    "1": [0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
    /*
    OOOO
    OXXO
    OOXO
    OOXO
    */
    "2": [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0],
    /*
    OXOO
    OXOO
    OXOO
    OXOO
    */
    "3": [0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
    /*
     OOOO
     OXXO
     OOXX
     OOOO 
     */
    "4": [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0],
    /*
     OOOO
     OXXO
     XXOO
     OOOO 
     */
    "5": [0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0],
    /*
     OOOO
     OXXO
     OOXO
     OOOO
    */
    "6": [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0],
};

/**
 * 基本图块
 * */
class Block {
    constructor(key: keyof blocksDataType) {
        this.data = blocksData[key];
    }

    protected data: Array<number>;
    /**
     * 当前图块的方向
     * */
    orientation: orientationType = 0;
    /**
     * 获取当前图块按方向旋转后的数据
     * */
    getData(): { clipX: number, clipY: number, width: number, height: number, data: Array<number> } {
        var newdata;
        var ori = this.orientation % 4; //实际的方向在旋转很多次后可能超过3，故除4取余
        //******关键点：矩阵的旋转
        switch (ori) {
            case 0://不变
                newdata = this.data.slice();
                break;
            case 1://90度
                newdata = new Array(16);
                for (var c = 0; c < 4; c++) {
                    for (var r = 0; r < 4; r++) {
                        var temp = this.data[c + (r * 4)];
                        var col = 3 - r;//行取余变列
                        var row = c;//列变行
                        newdata[col + (row * 4)] = temp;
                    }
                }
                break;
            case 2://180度
                newdata = new Array(16);
                for (var c = 0; c < 4; c++) {
                    for (var r = 0; r < 4; r++) {
                        var temp = this.data[c + (r * 4)];
                        //行列各自取余互换
                        var col = 3 - c;
                        var row = 3 - r;
                        newdata[col + (row * 4)] = temp;
                    }
                }
                break;
            case 3://270度
                newdata = new Array(16);
                for (var c = 0; c < 4; c++) {
                    for (var r = 0; r < 4; r++) {
                        var temp = this.data[c + (r * 4)];
                        var col = r;//行变列
                        var row = 3 - c;//列取余变行
                        newdata[col + (row * 4)] = temp;
                    }
                }
                break;
            default:
                break;
        }
        var clipX = 4, clipY = 4;
        var maxX = 0, maxY = 0;
        //计算图块的有效区域
        for (var c = 0; c < 4; c++) {
            for (var r = 0; r < 4; r++) {
                var temp = this.data[c * r];
                if (temp > 0) {
                    clipX = Math.min(clipX, c);
                    clipY = Math.min(clipY, r);
                    maxX = Math.max(maxX, c);
                    maxY = Math.max(maxY, r);
                }
            }
        }
        if (clipX > 3 || clipY > 3) {
            console.error(this.data);
            throw "error data.";
        }
        var result = {
            clipX: clipX,//当前数据的裁剪X位置
            clipY: clipY,
            width: maxX - clipX + 1,// 裁剪后的宽度
            height: maxY - clipY + 1,
            data: newdata
        };

        return result;
    }
}

//#endregion